package org.lemon.transport.mqtt.server.adaptors;

import com.google.gson.JsonElement;
import com.google.gson.JsonParser;
import com.google.gson.JsonSyntaxException;
import io.netty.buffer.ByteBuf;
import io.netty.handler.codec.mqtt.*;
import io.netty.util.internal.StringUtil;
import lombok.extern.slf4j.Slf4j;
import org.lemon.transport.context.adaptor.AdaptorException;
import org.lemon.transport.context.adaptor.JsonConverter;
import org.lemon.transport.context.data.device.MqttTopics;
import org.server.transport.gen.TransportProto;
import org.lemon.transport.mqtt.server.session.MqttDeviceAwareSessionContext;

import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.Optional;
import java.util.UUID;

@Slf4j
public class JsonMqttAdaptor implements MqttTransportAdaptor {

    protected static final Charset UTF8 = StandardCharsets.UTF_8;

    @Override
    public TransportProto.PostTelemetryMsg convertToPostTelemetry(MqttDeviceAwareSessionContext ctx, MqttPublishMessage inbound) throws AdaptorException {

        String payload = validatePayload(ctx.getSessionId(), inbound.payload(), false);
        try {
            return JsonConverter.convertToTelemetryProto(new JsonParser().parse(payload));
        } catch (IllegalStateException | JsonSyntaxException ex) {
            log.debug("Failed to decode post telemetry request", ex);
            throw new AdaptorException(ex);
        }
    }

    @Override
    public TransportProto.PostAttributeMsg convertToPostAttributes(MqttDeviceAwareSessionContext ctx, MqttPublishMessage inbound) throws AdaptorException {

        String payload = validatePayload(ctx.getSessionId(), inbound.payload(), false);
        try {
            return JsonConverter.convertToAttributesProto(new JsonParser().parse(payload));
        } catch (IllegalStateException | JsonSyntaxException ex) {
            log.debug("Failed to decode post attribute request", ex);
            throw new AdaptorException(ex);
        }
    }

    @Override
    public TransportProto.ProvisionDeviceRequestMsg convertToProvisionRequestMsg(MqttDeviceAwareSessionContext ctx, MqttPublishMessage inbound) throws AdaptorException {
        return null;
    }

    @Override
    public Optional<MqttMessage> convertToPublish(MqttDeviceAwareSessionContext deviceSessionCtx, TransportProto.ProvisionDeviceResponseMsg provisionResponseMsg) {
        return Optional.of(createMqttPublishMsg(deviceSessionCtx, MqttTopics.DEVICE_PROVISION_RESPONSE_TOPIC, JsonConverter.toJson(provisionResponseMsg)));
    }

    @Override
    public Optional<MqttMessage> convertToPublish(MqttDeviceAwareSessionContext deviceSessionCtx, TransportProto.ProvisionDeviceResponseMsg provisionResponseMsg, String topicName) {
        return Optional.of(createMqttPublishMsg(deviceSessionCtx, topicName, JsonConverter.toJson(provisionResponseMsg)));
    }

    @Override
    public Optional<MqttMessage> convertToPublish(MqttDeviceAwareSessionContext deviceSessionCtx, TransportProto.AttributeUpdateNotificationMsg notificationMsg, String topicName) throws AdaptorException {
        return Optional.of(createMqttPublishMsg(deviceSessionCtx, topicName, JsonConverter.toJson(notificationMsg)));
    }

    @Override
    public Optional<MqttMessage> convertToPublish(MqttDeviceAwareSessionContext deviceSessionCtx, TransportProto.GetAttributeResponseMsg attributesResponse, String topicName) throws AdaptorException {
        if (!StringUtil.isNullOrEmpty(attributesResponse.getError())) {
            throw new AdaptorException(attributesResponse.getError());
        } else {
            int requestId = attributesResponse.getRequestId();
            if (requestId >= 0) {
                return Optional.of(createMqttPublishMsg(deviceSessionCtx,
                        topicName + requestId,
                        JsonConverter.toJson(attributesResponse)));
            }
            return Optional.empty();
        }
    }

    public static JsonElement validateJsonPayload(UUID sessionId, ByteBuf payloadData) throws AdaptorException {
        String payload = validatePayload(sessionId, payloadData, false);
        try {
            return new JsonParser().parse(payload);
        } catch (JsonSyntaxException ex) {
            log.debug("Payload is in incorrect format: {}", payload);
            throw new AdaptorException(ex);
        }
    }

    protected MqttPublishMessage createMqttPublishMsg(MqttDeviceAwareSessionContext ctx, String topic, JsonElement json) {
        MqttFixedHeader mqttFixedHeader =
                new MqttFixedHeader(MqttMessageType.PUBLISH, false, ctx.getQoSForTopic(topic), false, 0);
        MqttPublishVariableHeader header = new MqttPublishVariableHeader(topic, ctx.nextMsgId());
        ByteBuf payload = ALLOCATOR.buffer();
        payload.writeBytes(json.toString().getBytes(UTF8));
        return new MqttPublishMessage(mqttFixedHeader, header, payload);
    }

    private static String validatePayload(UUID sessionId, ByteBuf payloadData, boolean isEmptyPayloadAllowed) throws AdaptorException {
        String payload = payloadData.toString(UTF8);
        if (payload == null) {
            log.debug("[{}] Payload is empty!", sessionId);
            if (!isEmptyPayloadAllowed) {
                throw new AdaptorException(new IllegalArgumentException("Payload is empty!"));
            }
        }
        return payload;
    }

    private int getRequestId(String topicName, String topic) {
        return Integer.parseInt(topicName.substring(topic.length()));
    }
}
